package com.magenta.maxoptra.yournavigation.org;

import com.magenta.maxoptra.dto.GeoLocationRecord;
import com.magenta.maxoptra.gis.*;
import com.magenta.maxoptra.gis.commons.Logger;
import com.magenta.maxoptra.gis.commons.StringUtils;
import com.magenta.maxoptra.gis.utils.DateTimeHelper;
import com.magenta.maxoptra.gis.utils.GetRoutePointsByCoordsProfiler;
import com.magenta.maxoptra.gis.utils.GetRouteProfiler;
import com.magenta.mrs.basegeocoding.BaseGeocoding;
import com.magenta.mrs.util.GeoInformationClient;
import org.apache.commons.httpclient.URIException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.SocketAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import static com.magenta.maxoptra.gis.GisParameter.base_url;
import static com.magenta.maxoptra.gis.GisParameter.use_shortest_route;
import static com.magenta.maxoptra.gis.MapProviderType.yournavigation;
import static com.magenta.maxoptra.gis.utils.GeoProviderHelper.getGeoDouble;

/**
 * Выполняет запрос информации о маршруте к сервису YourNavigation.Org
 * <p/>
 * Query sample:
 * http://www.yournavigation.org/api/dev/route.php?flat=53.205776854477&flon=50.116135068359&
 * tlat=53.215132030574&tlon=50.201622434082&v=bicycle&fast=0&layer=mapnik&instructions=1
 *
 * @author Zaur Muhametgaleev
 * @version 12/5/11 12:33 PM
 * @see "jira task MRS-4127: Add support of YourNavigation.Org routing provider"
 * @since Maxoptra 3 (GT)
 */
public class GeoProviderYournavigation extends BaseGeocoding<String> implements IGeoRouting, IGeoConfigurable {

    protected java.net.Proxy proxy;

    private Logger logger = new Logger(getClass());

    @Override
    public GeoRoute getRoute(GeographicCoordinates from, GeographicCoordinates to, VehicleType vehicleType) throws GeoException {
        GetRouteProfiler profiler = new GetRouteProfiler(getProviderName(), getAccountGlobalId(), isLoggingAllowed(), from, to);
        YourNavigationWS instance = YourNavigationWS.getInstance();
        try {
            TrafficRoad trafficRoad = instance.getDistance(proxy, getRoutingUrl(from, to, false), from.getLatitude(), from.getLongitude(), to.getLatitude(), to.getLongitude());
            long duration = (new Double(trafficRoad.getTime())).longValue()* DateTimeHelper.MSECS_IN_SECOND;
            double distance = trafficRoad.getDistance();
            GeoRoute geoRoute = null;
            if (distance <= 0) {
                geoRoute = new GeoRoute(GeoStatusCode.G_GEO_RESULT_IS_NULL.getCode(), GeoStatusCode.G_GEO_RESULT_IS_NULL.getCode(), from, to, profiler.getDuration());
            } else {
                geoRoute = new GeoRoute(distance, duration, from, to, profiler.getDuration());
            }
            profiler.logDuration(geoRoute);
            return geoRoute;
        } catch (Exception e) {
            profiler.logError(e.getMessage());
            throw new GeoException(e);
        }
    }

    @Override
    public List<GeoLocationRecord> getRoutePoints(GeoProviderSettings settings, GeographicCoordinates from, GeographicCoordinates to) throws GeoException {
        GetRoutePointsByCoordsProfiler profiler = new GetRoutePointsByCoordsProfiler(getProviderName(), getAccountGlobalId(), isLoggingAllowed(), from, to);
        List<GeoLocationRecord> result = new ArrayList<GeoLocationRecord>();
        try {
            YourNavigationWS instance = YourNavigationWS.getInstance();
            String requestResult = getRequestResult(from, to, true);
            if (!StringUtils.isEmpty(requestResult)) {
                try {
                    result.addAll(instance.extractRoutePointsFromRequest(requestResult));
                    profiler.logDuration(result.size());
                } catch (Exception e) {
                    throw new GeoException(e.getMessage());
                }
            }
        } catch (GeoException e) {
            profiler.logError(e.getMessage());
            throw e;
        }
        return result;
    }

    private String getRequestResult(GeographicCoordinates from, GeographicCoordinates to, boolean withPoints) {
        String requestResult = null;
        try {
            String encodedUrl = getRoutingUrl(from, to, withPoints);
            HttpGet get = new HttpGet(encodedUrl);
            HttpResponse response = client.execute(get);
            requestResult = EntityUtils.toString(response.getEntity());
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return requestResult;
    }

    private String getRoutingUrl(GeographicCoordinates from, GeographicCoordinates to, boolean withPoints) throws URIException {
        /*
        * Query sample:
        * http://www.yournavigation.org/api/dev/route.php?flat=53.205776854477&flon=50.116135068359&
        * tlat=53.215132030574&tlon=50.201622434082&v=bicycle&fast=0&layer=mapnik&instructions=1
        * */
        GisParameters properties = client.getProperties();
        String providerName = getProviderName();
        String baseUrl = getURL();
        String useShortestRouteStr = properties.get(use_shortest_route.withProvider(providerName));
        boolean useShortestRoute = Boolean.parseBoolean(useShortestRouteStr);

        if (StringUtils.isEmpty(baseUrl)) {
            throw new IllegalStateException("Routing url is not specified for provider: " + providerName);
        }
        String fLat = getGeoDouble(from.getLatitude());
        String fLon = getGeoDouble(from.getLongitude());
        String tLat = getGeoDouble(to.getLatitude());
        String tLon = getGeoDouble(to.getLongitude());
        int useFastest = !useShortestRoute ? 0 : 1;
        String request = MessageFormat.format(baseUrl, fLat, fLon, tLat, tLon, useFastest);
        request = request + "&geometry=" + (withPoints ? "1" : "0");

//        String enableLoggingStr = properties.getProperty(enableLogging.withProvider(getProviderName()));
//        boolean allowLogging = BooleanUtils.toBoolean(enableLoggingStr);
//        if (allowLogging) {
//            logger.info("Yournavigation url: " + request);
//        }
        return org.apache.commons.httpclient.util.URIUtil.encodeQuery(request);
    }

    private String getURL() {
        GisParameters properties = client.getProperties();
        String providerName = getProviderName();
        return properties.get(base_url.withProvider(providerName));
    }

    @Override
    public String getProviderName() {
        return yournavigation.name();
    }

    @Override
    protected boolean accuracyIsAccurateEnough(String accuracyThreshold, String accuracy) {
        return false;
    }

    @Override
    protected List<GeoAddress> doFindAddressByKeyword(GeoProviderSettings settings, String keyword, int i, List<String> accuracies) throws GeoException {
        throw new UnsupportedOperationException("doFindAddressByKeyword is not supported for GeoProviderYournavigation");
    }


    @Override
    public GeoRoute getRoute(List<GeographicCoordinates> coordinates, VehicleType vehicleType) throws GeoException {
        throw new UnsupportedOperationException("getRoute by list of coordinates is not supported for GeoProviderYournavigation");
    }

    @Override
    public List<GeoRoute> getRoute(GeographicCoordinates from, List<? extends GeographicCoordinates> to, VehicleType vehicleType, int timeout) throws GeoException {
        throw new UnsupportedOperationException("getRoute from coordinate to list of coordinates is not supported for GeoProviderYournavigation");
    }

    public synchronized List<GeoLocationRecord> getRoutePoints(GeoProviderSettings settings, List<GeographicCoordinates> coordinates) throws GeoException {
        List<GeoLocationRecord> result = new ArrayList<GeoLocationRecord>();
        if (coordinates != null && coordinates.size() > 1) {
            for (int i = 0; i < coordinates.size() - 1; i++) {
                result.addAll(getRoutePoints(settings, coordinates.get(i), coordinates.get(i + 1)));
            }
        }
        return result;
    }

    @Override
    public List<GeoAddress> doForwardGeocoding(GeoProviderSettings settings, GeoAddress address, int resultAddressLimit) throws GeoException {
        throw new UnsupportedOperationException("doForwardGeocoding is not supported for GeoProviderYournavigation");
    }

    @Override
    public void setClient(GeoInformationClient client) {
        this.client = client;
        HttpHost httpProxy = (HttpHost) client.getParams().getParameter(ConnRoutePNames.DEFAULT_PROXY);
        if (httpProxy != null) {
            String proxyHost = httpProxy.getHostName();
            int proxyPort = httpProxy.getPort();
            final Credentials credentials = ((DefaultHttpClient) client).getCredentialsProvider().getCredentials(
                    new AuthScope(proxyHost, proxyPort));

            SocketAddress socketAddress = new InetSocketAddress(proxyHost, proxyPort);
            this.proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, socketAddress);

            Authenticator.setDefault(new Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    PasswordAuthentication p = new PasswordAuthentication(credentials.getUserPrincipal().getName(),
                            credentials.getPassword().toCharArray());
                    return p;
                }
            });
        }
    }

    @Override
    public List<GeoRoute> getRoutes(GeographicCoordinates from, List<? extends GeographicCoordinates> to, VehicleType vehicleType, int timeout) throws GeoException {
        throw new UnsupportedOperationException("getRoutes from coordinate to list of coordinates is not supported for GeoProviderYournavigation");
    }

    @Override
    public List<GeoRoute> getCoordinateToCoordinateRoutes(List<GeographicCoordinates> coordinates) throws ServerUnavailableException {
        throw new UnsupportedOperationException("getCoordinateToCoordinateRoutes from coordinate to list of coordinates is not supported for " + getProviderName());
    }

    @Override
    public List<GeoRoute> getRoutes(GeographicCoordinates from, List<? extends GeographicCoordinates> to, int timeout) throws GeoException, ServerUnavailableException {
        return getRoutes(from, to, timeout);
    }

    @Override
    public List<GeoRoute> getRoute(GeographicCoordinates from, List<? extends GeographicCoordinates> to, int timeout) throws GeoException {
        return getRoute(from, to, null, timeout);
    }

    @Override
    public GeoRoute getRoute(List<GeographicCoordinates> coordinates) throws GeoException {
        return getRoute(coordinates, null);
    }

    @Override
    public synchronized GeoRoute getRoute(GeographicCoordinates from, GeographicCoordinates to) throws GeoException, ServerUnavailableException {
        return getRoute(from, to, null);
    }

}
